using System;
using System.IO;
using System.IO.Compression;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Yahoo.Yui.Compressor;

namespace Cache2
{
    public class CacheResult : ActionResult
    {
        private string _keyname;
        private string _version;
        private string _type;

        public CacheResult(string keyname, string version, string type)
        {
            this._keyname = keyname;
            this._version = version;
            if (type.ToLower().Contains("css"))
            {
                this._type = @"text/css";
            }

            if (type.ToLower().Contains("javascript"))
            {
                this._type = @"text/javascript";
            }
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            ScriptCombiner myCombiner = new ScriptCombiner(this._keyname, this._version, this._type);
            myCombiner.ProcessRequest(context.HttpContext, this._type);
        }
    }

    public static class CacheControllerExtensions
    {
        public static CacheResult RenderCacheResult(string keyname, string version, string type)
        {
            return new CacheResult(keyname, version, type);
        }
    }

    public class CacheController : Controller
    {
        #region Constructor Definitions

        public CacheController()

            : base()
        {

        }

        #endregion

        #region Method Definitions

        #region public

        public CacheResult CacheContent(string key, string version, string type)
        {
            return CacheControllerExtensions.RenderCacheResult(key, version, type);
        }

        //public CacheResult ClearCache()
        //{
        //    //LOGIC TO CLEAR OUT CACHE
        //}

        #endregion

        #endregion

    } // End class CacheController

    public class ScriptCombiner
    {
        #if RELEASE
        
        TimeSpan CACHE_DURATION = TimeSpan.FromDays(900);

        #endif 

        TimeSpan CACHE_DURATION = TimeSpan.FromDays(0);
        private System.Web.HttpContextBase context;
        private string _ContentType;
        private string _key;
        private string _version;

        public ScriptCombiner(string keyname, string version, string type)
        {
            this._ContentType = type;
            this._key = keyname;
            this._version = version;
        }

        public void ProcessRequest(System.Web.HttpContextBase context, string type)
        {
            

            this.context = context;
            HttpRequestBase request = context.Request;

            // Read setName, version from query string
            string setName = _key;
            string version = _version;
            string contentType = _ContentType;

            // Decide if browser supports compressed response
            bool isCompressed = this.CanGZip(context.Request);

            // If the set has already been cached, write the response directly from
            // cache. Otherwise generate the response and cache it
            if (!this.WriteFromCache(setName, version, isCompressed, contentType))
            {
                using (MemoryStream memoryStream = new MemoryStream(8092))
                {
                    // Decide regular stream or gzip stream based on whether the response can be compressed or not
                    //using (Stream writer = isCompressed ?  (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) : memoryStream)
                    using (Stream writer = isCompressed ? (Stream)(new GZipStream(memoryStream, CompressionMode.Compress)) : memoryStream)
                    {
                        // Read the files into one big string
                        StringBuilder allScripts = new StringBuilder();
                        foreach (string fileName in GetScriptFileNames(setName))
                        {
                            allScripts.Append(File.ReadAllText(context.Server.MapPath(fileName)));
                        }
                        // Minify the combined script files and remove comments and white spaces
                        string minified=string.Empty;

                        if (type == null && setName.ToLower().Contains("css"))
                            type = "css";

                        if (type.ToLower().Contains("css"))
                        {
                            minified = CssCompressor.Compress(allScripts.ToString());
                        }
                        else if (type.ToLower().Contains("javascript"))
                        {
                            minified = JavaScriptCompressor.Compress(allScripts.ToString());
                        }
                        

                        //var minifier = new JavaScriptMinifier();
                        //string minified = minifier.Minify(allScripts.ToString());

#if DEBUG
                        minified = allScripts.ToString();
                        if (minified==string.Empty)
                            throw new ApplicationException("Empty minified");
#endif

                        byte[] bts = Encoding.UTF8.GetBytes(minified);
                        writer.Write(bts, 0, bts.Length);
                    }
                    // Cache the combined response so that it can be directly written
                    // in subsequent calls

                    byte[] responseBytes = memoryStream.ToArray();
                    context.Cache.Insert(GetCacheKey(setName, version, isCompressed),
                        responseBytes, null, System.Web.Caching.Cache.NoAbsoluteExpiration,
                        CACHE_DURATION);

                    // Generate the response
                    this.WriteBytes(responseBytes, isCompressed, contentType);
                }
            }
        }

        ///// <summary>
        ///// Change all ~/ moments with real paths. 
        ///// </summary>
        ///// <param name="p"></param>
        ///// <returns></returns>
        //private string FixRelativePathsInCss(string inputCss)
        //{
        //    string absPath = System.Web.VirtualPathUtility.ToAbsolute("~/");
        //    inputCss = inputCss.Replace("~/", absPath);
        //    return inputCss;
        //}

        private bool WriteFromCache(string setName, string version, bool isCompressed, string ContentType)
        {
            byte[] responseBytes = context.Cache[GetCacheKey(setName, version, isCompressed)] as byte[];
            if (responseBytes == null || responseBytes.Length == 0)
                return false;
            this.WriteBytes(responseBytes, isCompressed, ContentType);
            return true;
        }

        private void WriteBytes(byte[] bytes, bool isCompressed, string ContentType)
        {
            HttpResponseBase response = context.Response;
            response.AppendHeader("Content-Length", bytes.Length.ToString());
            response.ContentType = ContentType;
            if (isCompressed)
                response.AppendHeader("Content-Encoding", "gzip");
            else
                response.AppendHeader("Content-Encoding", "utf-8");
            context.Response.Cache.SetCacheability(HttpCacheability.Public);
            context.Response.Cache.SetExpires(DateTime.Now.Add(CACHE_DURATION));
            context.Response.Cache.SetMaxAge(CACHE_DURATION);
            response.ContentEncoding = Encoding.Unicode;
            response.OutputStream.Write(bytes, 0, bytes.Length);
            response.Flush();
        }

        private bool CanGZip(HttpRequestBase request)
        {
            string acceptEncoding = request.Headers["Accept-Encoding"];
            if (!string.IsNullOrEmpty(acceptEncoding) &&
                 (acceptEncoding.Contains("gzip") || acceptEncoding.Contains("deflate")))
                return true;
            return false;
        }

        private string GetCacheKey(string setName, string version, bool isCompressed)
        {
            return "HttpCombiner." + setName + "." + version + "." + isCompressed;
        }

        public bool IsReusable
        {
            get { return true; }
        }

        // private helper method that return an array of file names inside the text file stored in App_Data folder
        private static string[] GetScriptFileNames(string setName)
        {
            var scripts = new System.Collections.Generic.List<string>();
            string setDefinition = System.Configuration.ConfigurationManager.AppSettings[setName] ?? "";
            string[] fileNames = setDefinition.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (string fileName in fileNames)
            {
                if (!String.IsNullOrEmpty(fileName))
                    scripts.Add(fileName);
            }
            return scripts.ToArray();
        }
    }
}